<template>
  <div class="app-container table-app-container instances-page">
    <el-row :gutter="8">
      <el-col :span="4">
        <el-tree ref="tree" class="filter-tree" :data="treeData" :props="defaultTreeProps" node-key="_id" :default-expanded-keys="treeExpandedKeys" @node-click="onTreeSelected" />
      </el-col>
      <el-col :span="20">
        <div class="fixed-table">
          <div class="fixed-table-actionbar">
            <TableFilter
              ref="filterForm"
              :inactive="oprIsInactive"
              :kw="kw"
              :dizdef-id="bizdef"
              @on-filter="searchContext"
              @on-export="exportExcel"
              @on-change="onResize"
              @on-download-tpl="downloadTpl"
              @on-import-success="importSuccess"
              @on-import-error="importError"
              @batch-remove="onBatchRemove"
            />
            <div class="action-bar">
              <el-button type="text" size="mini" title="添加" :disabled="oprIsInactive" icon="el-icon-plus" @click="add('')" />
              <el-button type="text" size="mini" title="编辑" :disabled="oprIsInactive" icon="el-icon-edit" @click="onEdit" />
              <el-button v-show="edited" type="text" size="mini" title="提交" icon="el-icon-check" @click="submit" />
              <el-button v-show="edited" type="text" size="mini" title="取消" icon="el-icon-switch-button" @click="cancel" />
            </div>
          </div>
          <PageableTable
            v-if="insideColumns.length > 0"
            :key="tableKey"
            ref="myTable"
            :loading="loading"
            :data="insideTableData"
            :total="totalElements"
            :page="currentPage"
            :page-size="pageSize"
            :table-height="tableHeight"
            class="fixed-table-table"
            :multiple-selected="true"
            @page-change="onPageChange"
            @sort-change="onSortChange"
            @selection-change="onSelectionChange"
            @row-click="onSelectionChange"
          >
            <el-table-column v-for="column in insideColumns" :key="column.key" :width="column.width" :label="column.title" :fixed="!!column.fixed" :prop="column.key" sortable="column.sortable">
              <template slot-scope="scope">
                <column-render v-if="column.render" :render="column.render" :row="scope.row" :column="column" :show-overflow-tooltip="true" />
                <span v-else>{{ scope.row[column.key] || '-' }}</span>
              </template>
            </el-table-column>
            <el-table-column fixed="right" label="操作" align="center" width="120" class-name="small-padding fixed-width">
              <template slot-scope="{row}">
                <router-link v-if="!!row._id" :to="'/cmdb/businesses/bizs/' + row._id" class="el-button el-button--text el-button--mini"><i class="el-icon-view" /></router-link>
                <el-button v-if="!!row._id" title="复制" type="text" size="mini" icon="el-icon-copy-document" @click="add(row)" />
                <el-button v-if="!!row._id" title="删除" type="text" size="mini" icon="el-icon-delete" @click="remove(row._id)" />
              </template>
            </el-table-column>
          </PageableTable>
        </div>
      </el-col>
    </el-row>

    <el-dialog
      :visible.sync="uploadResultDialogVisible"
      width="80%"
    >
      <span slot="title" class="result-error">
        <i class="el-icon-error">导入失败记录：{{ errorResultData.length }}</i>
      </span>
      <el-table
        v-if="uploadResultDialogVisible"
        :show-header="false"
        :data="errorResultData"
        height="400"
        style="width: 100%"
      >
        <el-table-column
          prop="code"
          label="唯一标识"
          width="180"
        />
        <el-table-column
          prop="name"
          label="名称"
          width="180"
        />
        <el-table-column
          prop="desc"
          label="描述"
          width="180"
        />
        <el-table-column
          prop="errmsg"
          label="错误"
        />
      </el-table>
    </el-dialog>
  </div>
</template>

<script>
import TableFilter from './components/tableFilter'
import PageableTable from '@neu/biz-core/components/PageableTable'
import databind from '@neu/biz-core/mixins/databind'
import { pureTree } from '@neu/cmdb-ui/api/schema'
import { schemaInstances } from '@neu/cmdb-ui/api/instance'
import { list as bizdefList, detail as bizdefDetail, downloadSchemaTpl } from '@neu/cmdb-ui/api/bizdef'
import { detail as instanceDetail, create, update, remove, batchRemove, pageByBizdef, exportByBizdef } from '@neu/cmdb-ui/api/bizs'
import { deepClone, parseTime } from '@neu/biz-core/utils'
import { isArray } from '@neu/biz-core/utils/utils'
import ValidatorSchema from 'async-validator'

var cn = {
  required: '%s 必填',
  string: {
    max: '%s 长度不能超过 %s'
  },
  number: {
    max: '%s 不能大于 %s'
  }
}

const getDefaultValue = (dtype, dvalue) => {
  return dvalue || (dtype === 'string' ? '' : (dtype === 'bool' ? false : (dtype === 'number' ? 0 : (dtype === 'date' ? '' : ''))))
}

const addFormItem = {
  name: '',
  src: '',
  dest: '',
  rela: '',
  attrs: []
}

export default {
  name: 'CmdbBizInstancePage',
  components: {
    TableFilter,
    PageableTable
  },
  // 加入混入，使表格混入更多的api操作
  mixins: [databind],
  props: {},
  data() {
    return {
      r: '',
      d: remove,
      ds: batchRemove,
      insideColumns: [],
      editId: null, // 当前被编辑的实例项。 默认null，新增实例时，editId=''，修改实例时，editId=具体id
      defaultTreeProps: {
        children: 'children',
        label: 'name'
      },
      // dialogFormVisible: false,
      treeData: [],
      context: {}, // 当前选中的模型，页面的所有操作都需要在某一模型下进行
      tableKey: '', // 控制表格的渲染，防止切换模型时，表格渲染变形
      addFormItem: { ...addFormItem },
      descriptor: {}, // 动态数据校验
      kw: '', // 模糊查询关键字,
      uploadResultDialogVisible: false,
      errorResultData: [],
      availableSchemas: null,
      availableInstances: []
    }
  },
  computed: {
    // 表格操作按钮是否激活
    oprIsInactive() {
      return !this.context._id
    },
    bizdef() {
      return this.context._id
    },
    // 判断是否有编辑项
    edited() {
      return this.context && this.editId !== null
    },
    treeExpandedKeys() {
      return this.context && this.context._id && [this.context._id]
    }
  },
  // 可能通过路由传递schemadef和kw两个参数
  // 如果传递schemadef参数，页面打开后，直接请求模型下所有实例
  // 如果传递kw，必须同时传递schemadef参数，否则无效
  created() {
    const bizdefId = this.$route.query.bizdefId
    if (this.$route.query.kw) {
      this.kw = this.$route.query.kw
    }
    // 必须先加载模型树，然后在请求具体模型下的实例
    bizdefList().then(response => {
      const data = response.data
      const treeData = data.map(e => { return { name: e.name, _id: e._id } })
      const bizdef = treeData.find(s => s._id === bizdefId)
      if (bizdef) {
        this.onTreeSelected(bizdef)
      }
      this.treeData = treeData
    })
  },
  methods: {
    loadOptions({ action }) {
      if (action === 'LOAD_ROOT_OPTIONS') {
        return pureTree().then(response => {
          this.availableSchemas = response.data
        })
      }
    },
    onSchemaChanged(val) {
      schemaInstances(val).then(response => {
        this.availableInstances = response.data
      })
    },
    responseFormatter(data) {
      const formatData = data.map(it => {
        const { _id, name, code, bizdef, instances, attrs } = it
        const bizs_instances = []
        instances.forEach(en => { bizs_instances.push(en._id) })
        const mAttrs = attrs.reduce((o, item) => { o[item.key] = item.value; return o }, {})
        const d = {
          _id,
          name,
          code,
          bizdef: bizdef._id,
          bizdefName: bizdef.name,
          bizs_instances: bizs_instances,
          ...mAttrs
        }
        // 保留记录的原始值，在取消编辑时，可恢复原始值
        d.v0 = Object.freeze(deepClone(d))
        return d
      })
      return formatData
    },
    // 表格编辑按钮点击事件处理
    onEdit() {
      if (isArray(this.selections)) {
        if (this.selections.length === 0) {
          this.$message({
            type: 'warning',
            message: '请选择操作数据！'
          })
          return
        }
        if (this.selections.length > 1) {
          this.$message({
            type: 'warning',
            message: '只能选择一条数据！'
          })
          return
        }
        // 更新当前被编辑实体的ID，自动传递至编辑表单
        this.editId = this.selections[0]._id

        // 放入初始化的资源
        this.availableInstances = []
        if (this.selections[0]._id) {
          instanceDetail(this.selections[0]._id).then(res => {
            this.availableInstances = res.data.instances
          })
        }
      }
    },
    // 编辑资产对话框确认事件处理
    ok() {
      // 直接调用表单资源的保存方法
      this.$refs.editForm.save().then(response => {
        if (response === false) return
        this.$message({
          showClose: true,
          message: '操作成功！',
          type: 'success'
        })
        // 调用pageable-table资源的mixin方法refresh()，更新表格
        this.refresh()
      })
    },
    submit() {
      const row = this.insideTableData.find(it => it._id === this.editId)
      const { _id, ...attrs } = row
      const updated = _id !== ''

      const descriptor = {
        code: {
          required: true,
          message: '唯一标识 不能为空'
        },
        name: {
          required: true,
          message: '名称 不能为空'
        }
      }
      this.insideColumns.forEach(c => {
        const attrDef = this.context.attrs.find(it => it.field === c.key)
        // 防止未定义属性出现在实例中
        if (attrDef) {
          descriptor[attrDef.field] = []
          if (attrDef.notnullable) {
            const rule = {
              required: true,
              message: attrDef.name + ' 不能为空'
            }
            if (attrDef.dtype === 'bool') {
              rule['type'] = 'boolean'
            } else if (attrDef.dtype === 'string' || attrDef.dtype === 'number' || attrDef.dtype === 'date' || attrDef.dtype === 'enum') {
              rule['type'] = attrDef.dtype
            }
            descriptor[attrDef.field].push(rule)
          }

          if (attrDef.dtype === 'string' && attrDef.len > 0) {
            const rule = {
              type: 'string',
              max: attrDef.len,
              message: attrDef.name + ' 长度不能超过 ' + attrDef.len
            }
            descriptor[attrDef.field].push(rule)
          }

          if (attrDef.dtype === 'enum') {
            const rule = {
              type: 'enum',
              enum: attrDef.enums.map(a => a.value),
              message: attrDef.name + ' 非法枚举值'
            }
            descriptor[attrDef.field].push(rule)
          }
        }
      })

      var validator = new ValidatorSchema(descriptor)
      validator.messages(cn)
      validator.validate(attrs, (errors, fields) => {
        if (errors) {
          var messages = ''
          errors.forEach(e => { messages += '<div>' + e.message + '</div>' })
          this.$message({
            dangerouslyUseHTMLString: true,
            message: messages,
            type: 'error'
          })
        } else {
          if (updated) {
            update(_id, attrs).then(response => {
              this.editId = null
            })
          } else {
            create(attrs).then(response => {
              row._id = response.data._id
              this.editId = null
            })
          }
        }
      })
    },
    cancel() {
      const editedRowIdx = this.insideTableData.findIndex(
        it => it._id === this.editId
      )

      if (this.editId === '') {
        this.insideTableData.splice(editedRowIdx, 1)
      } else {
        const row = this.insideTableData[editedRowIdx]
        for (const k in row.v0) {
          row[k] = row.v0[k]
        }
      }
      this.editId = null
    },
    // 点击树、路由传递模型参数时，都会调用
    onTreeSelected: async function(data, node) {
      this.loading = true
      this.tableKey = data._id
      const routePath = this.$route.path
      this.$router.push({ routePath, query: { bizdefId: data._id }})
      if (data.children && data.children.length > 0) return
      // 当前选中的模型，页面的所有操作都需要在某一模型下操作
      this.context = data
      const result = await bizdefDetail(data._id)
      const bizdef = result.data
      this.insideColumns = []
      const columns = [
        { title: 'ID', key: '_id', hide: true },
        {
          title: '唯一标识',
          width: '180',
          fixed: false,
          key: 'code',
          render: (h, params) => {
            return params.row._id === this.editId && this.editId === '' ? (
              <div>
                <string-input v-model={params.row.code} maxlen={255}></string-input>
              </div>
            ) : (
              <router-link to={'/cmdb/businesses/bizs/' + params.row._id} class={'el-link el-link--primary'}>{params.row.code}</router-link>
            )
          }
        },
        {
          title: '名称',
          width: '180',
          key: 'name',
          render: (h, params) => {
            return (params.row._id === this.editId) ? (
              <div>
                <string-input v-model={params.row.name} maxlen={255}></string-input>
              </div>
            ) : (
              <router-link to={'/cmdb/businesses/bizs/' + params.row._id} class={'el-link el-link--primary'}>{params.row.name}</router-link>
            )
          }
        },
        {
          title: '业务模型',
          width: '120',
          key: 'bizdefName',
          render: (h, params) => {
            return (
              <router-link to={'/cmdb/businesses/bizdefs/' + params.row.bizdef} class={'el-link el-link--primary'}>{ params.row.bizdefName }</router-link>
            )
          }
        },
        {
          title: '实例',
          width: '180',
          key: '_instances',
          render: (h, params) => {
            return params.row._id === this.editId ? (
              <div>
                <treeselect v-model={params.row.schema} multiple={false} options={this.availableSchemas} load-options={this.loadOptions} disable-branch-nodes={true} append-to-body={true} id-key='code' label-key='name' placeholder='请选择类别' onInput={this.onSchemaChanged}/>
                <multiple-select v-model={params.row.bizs_instances} options={this.availableInstances}/>
              </div>
            ) : (
              <div>
                {params.row.bizs_instances ? params.row.bizs_instances.length : 0 } 个
              </div>
            )
          }
        }
      ]
      let mAttrs = []
      if (bizdef.attrs) {
        mAttrs = [...bizdef.attrs]
      }

      this.context.attrs = mAttrs
      mAttrs.forEach(attr => {
        columns.push({
          title: attr.name,
          width: '120',
          key: attr.field,
          render: (h, params) => {
            if (params.row._id !== this.editId) {
              if (attr.dtype === 'bool') {
                return (
                  <el-tag type='info'>{ params.row[attr.field] ? '是' : '否'}</el-tag>
                )
              } else if (attr.dtype === 'date') {
                return h('span', parseTime(params.row[attr.field]))
              } else if (attr.dtype === 'enum') {
                const enumItem = attr.enums.find(e => e.value === params.row[attr.field])
                return h('span', enumItem ? enumItem.label : '')
              } else if (attr.dtype === 'foreign') {
                return (
                  <router-link to={'/cmdb/businesses/bizs/' + params.row[attr.field]} class={'el-link el-link--primary'}>
                    <foreign-select v-model={params.row[attr.field]} disabled={true} schema={attr.dvalue} width={150} />
                  </router-link>
                )
              } else {
                return h('span', params.row[attr.field])
              }
            }

            if (attr.dtype === 'string') {
              return (
                <string-input v-model={params.row[attr.field]} maxlen={attr.len} />
              )
            } else if (attr.dtype === 'number') {
              return (
                <number-input v-model={params.row[attr.field]} />
              )
            } else if (attr.dtype === 'bool') {
              return (
                <bool-checkbox v-model={params.row[attr.field]} />
              )
            } else if (attr.dtype === 'date') {
              return (
                <date-input v-model={params.row[attr.field]} />
              )
            } else if (attr.dtype === 'enum') {
              return (
                <enum-select v-model={params.row[attr.field]} options={attr.enums} />
              )
            } else if (attr.dtype === 'datasource') {
              return (
                <datasource-select v-model={params.row[attr.field]} url={attr.datasource.url} valueKey={attr.datasource.valueKey} labelKey={attr.datasource.labelKey} responseKey={attr.datasource.responseKey} width={150} />
              )
            } else if (attr.dtype === 'foreign') {
              return (
                <foreign-select v-model={params.row[attr.field]} schema={attr.dvalue} />
              )
            }
          }
        })
      })

      this.insideColumns = deepClone(columns)
      this.r = pageByBizdef(data._id)
      this.filter({ s_kw: this.kw })
    },
    add(copy) {
      this.$refs.myTable.getEleTable().clearSelection()
      this.editId = ''
      const item = {}
      this.insideColumns.forEach(c => {
        const attrDef = this.context.attrs.find(it => it.field === c.key)
        const defaultValue = attrDef ? getDefaultValue(attrDef.dtype, attrDef.dvalue) : ''
        item[c.key] = copy && c.key !== '_id' && c.key !== '_instances' ? copy[c.key] : c.key === '_instances' ? [] : defaultValue
      })
      item.bizdef = this.context._id
      item.bizdefName = this.context.name
      this.insideTableData.push(item)
      this.$refs.myTable.getEleTable().toggleRowSelection(item, true)
    },
    searchContext(query) {
      query = { ...query }
      this.filter(query)
    },
    exportExcel(query) {
      query = { ...query }
      exportByBizdef(this.context._id, query).then(response => {
        const blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8' })
        let filename = response.headers['content-disposition']
        if (filename) {
          filename = decodeURI(filename.split('=')[1]) // 提取文件名
        } else {
          filename = '导出信息.xlsx'
        }
        if ('download' in document.createElement('a')) { // 非IE下载
          const elink = document.createElement('a')
          elink.download = filename
          elink.style.display = 'none'
          elink.href = URL.createObjectURL(blob)
          document.body.appendChild(elink)
          elink.click()
          URL.revokeObjectURL(elink.href) // 释放URL 对象
          document.body.removeChild(elink)
        } else { // IE10+下载
          navigator.msSaveBlob(blob, filename)
        }
      })
    },
    downloadTpl() {
      const that = this
      downloadSchemaTpl(this.context._id).then(response => {
        const blob = new Blob([response.data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8' })
        let filename = response.headers['content-disposition']
        if (filename) {
          filename = decodeURI(filename.split('=')[1]) // 提取文件名
        } else {
          filename = that.context.name + '导出信息.xlsx'
        }
        if ('download' in document.createElement('a')) { // 非IE下载
          const elink = document.createElement('a')
          elink.download = filename
          elink.style.display = 'none'
          elink.href = URL.createObjectURL(blob)
          document.body.appendChild(elink)
          elink.click()
          URL.revokeObjectURL(elink.href) // 释放URL 对象
          document.body.removeChild(elink)
        } else { // IE10+下载
          navigator.msSaveBlob(blob, filename)
        }
      })
    },
    importSuccess() {
      this.filter()
    },
    importError(data) {
      this.errorResultData = data.map(it => {
        const { code, name, desc } = it.op
        const errmsg = it.errmsg
        return { code, name, desc, errmsg }
      })
      this.uploadResultDialogVisible = true
      this.filter()
    },
    onBatchRemove() {
      this.remove(this.selections.map(it => { return { id: it._id } }))
    },
    getConfirmContent() {
      return '删除实例将导致关系全部删除，确定要删除吗？'
    }
  }
}
</script>

<style>
.instances-page .result-error {
  color: rgb(245, 108, 108);
}

.instances-page .el-tree-node__children {
  overflow-x: auto!important;
}
</style>
